//
// Created by admin on 2024/8/2.
//

#include "CircularBuffer.h"
#include <string.h>
#include "stdio.h"

CircularBuffer::CircularBuffer(size_t size, bool cover) :
	_cover(cover) {
	if (size < 8) size = 8;

	memset(&ll, 0, sizeof(Circular_t));

	buffer         = new uint8_t[size];
	_size          = size;
	_is_static_buf = false;
}

CircularBuffer::CircularBuffer(uint8_t* buf, size_t size, bool cover) :
	_cover(cover) {
	if (buf == NULL || size == 0) return;

	memset(buf, 0, sizeof(uint8_t) * size);
	memset(&ll, 0, sizeof(Circular_t));

	buffer         = buf;
	_size          = size;
	_is_static_buf = true;
}

CircularBuffer::~CircularBuffer() {
	if (!_is_static_buf) {
		delete[] buffer;
	}
	_size = 0;
}

/**
 * Get used cache space size
 * @return push data length
 */
size_t CircularBuffer::available() {
	if (ll.full) {
		return _size;
	}

	size_t size = (_size + ll.head - ll.tail) % _size;

	return size;
}

size_t CircularBuffer::push(uint8_t* data, size_t len) {
	if (buffer == nullptr) {
		return 0;
	}

	if (data == nullptr || len == 0) {
		return 0;
	}

	/* Calc available push size */
	uint32_t usable_len = _size - available();

	if (_cover) {
		/**
		 * discard the before data
		 */
		// size_t before = len;
		// len = len > _size ? _size : len;
		// before -= len;
		// data = data + before;

		len = len > _size ? _size : len;
	}
	else {
		len = len > usable_len ? usable_len : len;
		if (len == 0) {
			return 0;
		}
	}

	uint32_t cp_size = 0;

	if (ll.head + len > _size - 1) {
		cp_size = _size - ll.head;
		memcpy(&buffer[ll.head], data, cp_size);
		ll.head = (ll.head + cp_size) % _size;

		data = data + cp_size;
		len  = len - cp_size;

		if (cp_size >= usable_len) {
			ll.full = true;
			ll.overflow++;
			ll.tail = ll.head;
			if (!_cover) return cp_size;
		}

		if (len == 0) {
			/* Data push over */
			return cp_size;
		}
	}

	memcpy(&buffer[ll.head], data, len);
	ll.head = (ll.head + len) % _size;

	if ((len + cp_size) >= usable_len) {
		ll.overflow++;
		ll.full = true;
		ll.tail = ll.head;
	}

	return len + cp_size;
}

size_t CircularBuffer::pull(uint8_t* data, size_t len) {
	if (buffer == nullptr) {
		return 0;
	}

	if (data == nullptr || len == 0) {
		return 0;
	}

	if (available() == 0) {
		return 0;
	}

	uint32_t usable_len = available();

	len = len > usable_len ? usable_len : len;

	uint32_t cp_size = 0;
	if (ll.tail + len > _size - 1) {
		cp_size = _size - ll.tail;
		memcpy(data, &buffer[ll.tail], cp_size);
		ll.tail = (ll.tail + cp_size) % _size;

		len     = len - cp_size;
		data    = data + cp_size;
		ll.full = false;

		if (ll.tail == ll.head) {
			return cp_size;
		}

		if (len == 0) {
			/* Data pull over */
			return cp_size;
		}
	}

	memcpy(data, &buffer[ll.tail], len);
	ll.tail = (ll.tail + len) % _size;

	ll.full = false;
	return len + cp_size;
}

int CircularBuffer::peek() {
	if (available()) {
		return buffer[ll.tail];
	}

	return -1;
}

size_t CircularBuffer::pop(size_t len) {
	if (len == 0) {
		return 0;
	}

	size_t used = available();
	len         = len > used ? used : len;

	pullEnd(len);
	return len;
}

void CircularBuffer::flush() {
	memset(&ll, 0, sizeof(Circular_t));
	memset(buffer, 0, _size);
}

size_t CircularBuffer::readToKeyword(const char* key, uint8_t* data, size_t len, size_t start_index) {
	if (key == nullptr || data == nullptr || len == 0) {
		return 0;
	}
	uint16_t key_len  = strlen(key);
	size_t   find_len = available();

	if (find_len < key_len) {
		return 0;
	}

	size_t p_read = (ll.tail + start_index) % _size;
	size_t tmp    = find_len;

	uint8_t found = false;
	while (tmp--) {
		if (buffer[p_read] == key[0]) {
			if (p_read + key_len > _size) {
				uint16_t len_1 = _size - p_read;
				uint16_t len_2 = key_len - len_1;

				if (strncmp((char*)&buffer[p_read], key, len_1) == 0 &&
					strncmp((char*)buffer, key + len_1, len_2) == 0) {
					found = true;
					break;
				}
			}
			else {
				if (strncmp((char*)&buffer[p_read], key, key_len) == 0) {
					found = true;
					break;
				}
			}
		}

		p_read = (p_read + 1) % _size;
	}

	if (!found) {
		return 0;
	}

	pop(start_index);

	size_t copy_len = find_len - tmp;

	if (len < copy_len) {
		copy_len = len;
	}

	return pull(data, copy_len);
}

size_t CircularBuffer::readToKeyword(const char* head, const char* tail, uint8_t* data, size_t len) {
	if (head == nullptr || tail == nullptr || data == nullptr || len == 0) {
		return 0;
	}

	uint16_t head_len = strlen(head);
	uint16_t tail_len = strlen(tail);
	size_t   find_len = available();

	if (find_len < head_len + tail_len) {
		return 0;
	}

	size_t p_read = ll.tail;
	size_t tmp    = find_len;

	uint8_t found_head = false;
	uint8_t found_tail = false;
	while (tmp--) {
		if (buffer[p_read] == head[0]) {
			if (p_read + head_len > _size) {
				uint16_t len_1 = _size - p_read;
				uint16_t len_2 = head_len - len_1;

				if (strncmp((char*)&buffer[p_read], head, len_1) == 0 &&
					strncmp((char*)buffer, head + len_1, len_2) == 0) {
					found_head = true;
					break;
				}
			}
			else {
				if (strncmp((char*)&buffer[p_read], head, head_len) == 0) {
					found_head = true;
					break;
				}
			}
		}

		p_read = (p_read + 1) % _size;
	}

	if (!found_head) {
		return 0;
	}

	/* remove the data between form ll.tail to "head" */
	size_t remove_len = find_len - tmp - 1;

	return readToKeyword(tail, data, len, remove_len);
}

void CircularBuffer::pushStart() {
	ll.pushLock = true;
}

/**
 * get push buffer head pointer.
 * @return buffer pointer
 */
uint8_t* CircularBuffer::head() {
	if (buffer == nullptr) {
		return nullptr;
	}

	return &buffer[ll.head];
}

size_t CircularBuffer::getPushLineSize() {
	size_t free_len = _size - available();

	free_len = ll.head + free_len > _size ? _size - ll.head : free_len;

	return free_len;
}

void CircularBuffer::pushEnd(size_t len) {
	size_t free_len = _size - available();

	ll.head = (ll.head + len) % _size;

	if (free_len <= len) {
		ll.full = true;
		ll.tail = ll.head;

		ll.overflow++;
	}

	ll.pushLock = false;
}

void CircularBuffer::pullStart() {
	ll.pullLock = true;
}

uint8_t* CircularBuffer::tail() {
	if (buffer == nullptr) {
		return nullptr;
	}

	return &buffer[ll.tail];
}

size_t CircularBuffer::getPullLineSize() {
	size_t len = available();

	if (len == 0) return 0;

	len = (ll.tail + len > _size ? _size - ll.tail : len);

	return len;
}

void CircularBuffer::pullEnd(size_t len) {
	ll.tail = (ll.tail + len) % _size;

	if (len > 0) ll.full = false;

	ll.pullLock = false;
}

CircularBuffer& CircularBuffer::operator=(const char* cstr) {
	flush();
	if (cstr) {
		push(cstr);
	}
	return *this;
}

CircularBuffer& CircularBuffer::operator+=(CircularBuffer& rhs) {
	auto len = rhs.available();

	rhs.pullStart();
	auto pull_len = push(rhs.tail(), rhs.getPullLineSize());
	rhs.pullEnd(pull_len);

	len -= pull_len;

	if (len > 0) {
		rhs.pullStart();
		pull_len = push(rhs.tail(), rhs.getPullLineSize());
		rhs.pullEnd(pull_len);
	}

	return *this;
}
